{
 "metadata": {
  "name": "",
  "signature": "sha256:ea6c0cbcfbf07c2ca0a12cc5dca7f8848621156ae9d1b5f64d70d854656fcb5d"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "import numpy as np\n",
      "import zlib\n",
      "import struct\n",
      " \n",
      "def make_png(data):\n",
      "    \"\"\"\n",
      "    Convert numpy array to PNG byte array.\n",
      "    \n",
      "    *data* must be (H, W, 4) with dtype=ubyte    \n",
      "    \"\"\"\n",
      "    \n",
      "    # www.libpng.org/pub/png/spec/1.2/PNG-Structure.html\n",
      "    header = b'\\x89PNG\\x0d\\x0a\\x1a\\x0a' # header\n",
      " \n",
      "    def mkchunk(data, name):\n",
      "        if isinstance(data, np.ndarray):\n",
      "            size = data.nbytes\n",
      "        else:\n",
      "            size = len(data)\n",
      "        chunk = np.empty(size + 12, dtype=np.ubyte)\n",
      "        chunk.data[0:4] = struct.pack('!I', size)\n",
      "        chunk.data[4:8] = name # b'CPXS' # critical, public, standard, safe\n",
      "        chunk.data[8:8+size] = data\n",
      "        chunk.data[-4:] = struct.pack('!i', zlib.crc32(chunk[4:-4]))\n",
      "        return chunk\n",
      " \n",
      "    # www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR\n",
      "    ctyp = 0b0110  # alpha, color\n",
      "    h, w = data.shape[:2]\n",
      "    depth = data.itemsize * 8\n",
      "    ihdr = struct.pack('!IIBBBBB', w, h, depth, ctyp, 0, 0, 0)\n",
      "    c1 = mkchunk(ihdr, 'IHDR')\n",
      " \n",
      "    # www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IDAT\n",
      "    idat = np.empty((h, w*4 + 1), dtype=np.ubyte) # insert filter byte at each scanline\n",
      "    idat[:, 1:] = data.reshape(h, w*4)\n",
      "    idat[:, 0] = 0\n",
      "    c2 = mkchunk(zlib.compress(idat), 'IDAT')\n",
      " \n",
      "    c3 = mkchunk(np.empty((0,), dtype=np.ubyte), 'IEND')\n",
      " \n",
      "    # concatenate\n",
      "    lh = len(header)\n",
      "    png = np.empty(lh + c1.nbytes + c2.nbytes + c3.nbytes, dtype=np.ubyte)\n",
      "    png.data[:lh] = header\n",
      "    p = lh\n",
      "    for chunk in (c1, c2, c3):\n",
      "        png[p:p+len(chunk)] = chunk\n",
      "        p += chunk.nbytes\n",
      " \n",
      "    return png"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 1
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "from IPython.html.widgets import ImageWidget # Widget definitions\n",
      "from IPython.display import display # Used to display widget in the notebook\n",
      "from IPython.utils.traitlets import Unicode, Bool # Used to declare attributes of our widget\n",
      "\n",
      "#from PIL import Image # Used to convert screenshot into PNG image\n",
      "\n",
      "import sys\n",
      "import base64 # Used to encode PNG image - for displaying with javascript\n",
      "import cStringIO # Used to save PNG image onto a buffer\n",
      "import numpy as np\n",
      "import OpenGL.GL as gl\n",
      "import OpenGL.GLUT as glut\n",
      "\n",
      "from vispy.gloo import Program, VertexBuffer, IndexBuffer\n",
      "from vispy.util.transforms import perspective, translate, rotate\n",
      "from vispy.util.cube import cube\n",
      "from vispy.gloo import gl"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 2
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Global variables\n",
      "vertex = \"\"\"\n",
      "uniform mat4 model;\n",
      "uniform mat4 view;\n",
      "uniform mat4 projection;\n",
      "attribute vec3 position;\n",
      "attribute vec4 color;\n",
      "varying vec4 v_color;\n",
      "void main()\n",
      "{\n",
      "    v_color = color;\n",
      "    gl_Position = projection * view * model * vec4(position,1.0);\n",
      "}\n",
      "\"\"\"\n",
      "\n",
      "fragment = \"\"\"\n",
      "varying vec4 v_color;\n",
      "void main()\n",
      "{\n",
      "    gl_FragColor = v_color;\n",
      "}\n",
      "\"\"\"\n",
      "phi, theta = 0, 0"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 3
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Screenshot widget\n",
      "class Screenshot(ImageWidget):\n",
      "    _view_name = Unicode(\"Screenshot\", sync=True)\n",
      "    # base64 encoded value of the PNG image\n",
      "    value = Unicode(sync=True)\n",
      "    # Listening screenshot updates\n",
      "    update = Bool(sync=True)\n",
      "    \n",
      "    # This event is triggered when Vispy display function sets update to True\n",
      "    def _update_changed(self, name, value):\n",
      "        self.update = False\n",
      "        self.screenshot()\n",
      "    \n",
      "    # Take a screenshot, convert it to PNG, encode it with b64\n",
      "    def screenshot(self):\n",
      "        \"\"\" Take a screenshot using glReadPixels.\"\"\"\n",
      "        x, y, w, h = 0, 0, 500, 500\n",
      "        gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 1) # PACK, not UNPACK\n",
      "        img = gl.glReadPixels(x, y, w, h, gl.GL_RGB, gl.GL_UNSIGNED_BYTE)\n",
      "        gl.glPixelStorei(gl.GL_PACK_ALIGNMENT, 4)\n",
      "\n",
      "        # reshape, flip, and return\n",
      "        if not isinstance(img, np.ndarray):\n",
      "            img = np.frombuffer(img, np.uint8)\n",
      "        img.shape = h, w, 3\n",
      "        img= np.flipud(img)\n",
      "        \n",
      "        # To PNG\n",
      "        #shot = Image.fromarray(img)\n",
      "        # Save the image onto buffer\n",
      "        #buf = cStringIO.StringIO()\n",
      "        #shot.save(buf, \"PNG\") ### FIXME: VERY SLOW! ###\n",
      "        # Open it again and encode with base64\n",
      "        #self.value = buf.getvalue().encode(\"base64\")\n",
      "        #self.value = make_png(img).tostring().encode(\"base64\")\n",
      "        #buf.close()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 4
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%%html\n",
      "<canvas id=\"canvascube\" width=\"500\" height=\"500\"></canvas>"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "html": [
        "<canvas id=\"canvascube\" width=\"500\" height=\"500\"></canvas>"
       ],
       "metadata": {},
       "output_type": "display_data",
       "text": [
        "<IPython.core.display.HTML object>"
       ]
      }
     ],
     "prompt_number": 5
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "%%javascript\n",
      "\n",
      "require([\"widgets/js/widget\"], function(WidgetManager){\n",
      "    \n",
      "    var Screenshot = IPython.DOMWidgetView.extend({\n",
      "        \n",
      "        render: function(){\n",
      "        },\n",
      "        \n",
      "        // Update, whenever value attribute of our widget changes\n",
      "        update : function(){\n",
      "            var canvas2d = document.getElementById(\"canvascube\").getContext(\"2d\");\n",
      "            var img = new Image();\n",
      "            var istr = this.model.get(\"value\");\n",
      "            img.src = \"data:image/png;base64,\" + istr;\n",
      "            canvas2d.drawImage(img, 0, 0);\n",
      "        },\n",
      "    })\n",
      "    WidgetManager.register_widget_view(\"Screenshot\", Screenshot);\n",
      "});"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "javascript": [
        "\n",
        "require([\"widgets/js/widget\"], function(WidgetManager){\n",
        "    \n",
        "    var Screenshot = IPython.DOMWidgetView.extend({\n",
        "        \n",
        "        render: function(){\n",
        "        },\n",
        "        \n",
        "        // Update, whenever value attribute of our widget changes\n",
        "        update : function(){\n",
        "            var canvas2d = document.getElementById(\"canvascube\").getContext(\"2d\");\n",
        "            var img = new Image();\n",
        "            var istr = this.model.get(\"value\");\n",
        "            img.src = \"data:image/png;base64,\" + istr;\n",
        "            canvas2d.drawImage(img, 0, 0);\n",
        "        },\n",
        "    })\n",
        "    WidgetManager.register_widget_view(\"Screenshot\", Screenshot);\n",
        "});"
       ],
       "metadata": {},
       "output_type": "display_data",
       "text": [
        "<IPython.core.display.Javascript object>"
       ]
      }
     ],
     "prompt_number": 6
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "def disp():\n",
      "    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)\n",
      "    program.draw(gl.GL_TRIANGLES, indices)\n",
      "    glut.glutSwapBuffers()\n",
      "    # Poke the widget so it updates the screenshot\n",
      "    screen.update = True\n",
      "\n",
      "def reshape(width, height):\n",
      "    gl.glViewport(0, 0, width, height)\n",
      "    projection = perspective(45.0, width / float(height), 2.0, 10.0)\n",
      "    program['projection'] = projection\n",
      "\n",
      "def timer(fps):\n",
      "    global theta, phi\n",
      "    theta += .5\n",
      "    phi += .5\n",
      "    model = np.eye(4, dtype=np.float32)\n",
      "    rotate(model, theta, 0, 0, 1)\n",
      "    rotate(model, phi, 0, 1, 0)\n",
      "    program['model'] = model\n",
      "    glut.glutTimerFunc(1000 / fps, timer, fps)\n",
      "    glut.glutPostRedisplay()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 7
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "# Our widget\n",
      "screen = Screenshot()\n",
      "\n",
      "glut.glutInit(sys.argv)\n",
      "glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH)\n",
      "glut.glutCreateWindow('Colored Cube')\n",
      "glut.glutReshapeWindow(500, 500)\n",
      "glut.glutReshapeFunc(reshape)\n",
      "glut.glutDisplayFunc(disp)\n",
      "glut.glutTimerFunc(1000 / 60, timer, 60)\n",
      "\n",
      "# Build cube data\n",
      "# --------------------------------------\n",
      "V, I, _ = cube()\n",
      "vertices = VertexBuffer(V)\n",
      "indices = IndexBuffer(I)\n",
      "program = []\n",
      "\n",
      "# Build program\n",
      "# --------------------------------------\n",
      "program = Program(vertex, fragment)\n",
      "program.bind(vertices)\n",
      "\n",
      "# Build view, model, projection & normal\n",
      "# --------------------------------------\n",
      "view = np.eye(4, dtype=np.float32)\n",
      "model = np.eye(4, dtype=np.float32)\n",
      "projection = np.eye(4, dtype=np.float32)\n",
      "translate(view, 0, 0, -5)\n",
      "program['model'] = model\n",
      "program['view'] = view\n",
      "\n",
      "# OpenGL initalization\n",
      "# --------------------------------------\n",
      "gl.glClearColor(0.30, 0.30, 0.35, 1.00)\n",
      "gl.glEnable(gl.GL_DEPTH_TEST)\n",
      "\n",
      "# Start\n",
      "# --------------------------------------\n",
      "# display our widget\n",
      "display(screen)\n",
      "# main loop\n",
      "glut.glutMainLoop()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": [
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "WARNING: texcoord has not been bound\n"
       ]
      },
      {
       "output_type": "stream",
       "stream": "stderr",
       "text": [
        "WARNING:vispy:texcoord has not been bound\n"
       ]
      },
      {
       "output_type": "stream",
       "stream": "stdout",
       "text": [
        "WARNING: normal has not been bound\n"
       ]
      },
      {
       "output_type": "stream",
       "stream": "stderr",
       "text": [
        "WARNING:vispy:normal has not been bound\n"
       ]
      }
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [],
     "language": "python",
     "metadata": {},
     "outputs": [],
     "prompt_number": 7
    }
   ],
   "metadata": {}
  }
 ]
}